import pyamf
import pyamf.remoting.gateway
from gluon.fileutils import check_credentials

# Added the same authentication here as used in the appadmin controller, so
# that you need to be logged in as an admin to be able to use any of these
# methods.  This does NOT gracefully fail - the swf file throws a fit...
#     -- Brennan Todd
if request.env.remote_addr!=request.env.http_host.split(':')[0]: 
    raise HTTP(400)
if not check_credentials(request):
    raise HTTP(404)

# This empty base function allows the Flash/Flex application to set an initial connection
def base(): return


def getDBs(null):
    dbs = []
    for k,v in globals().items():
        if isinstance(v, SQLDB):
           dbs.append(k)
    return dbs

def getDBTypes(null):
    return ['sqlite','mysql','postgres','oracle']

def getTables(model):
    try: db = eval(model)
    except: return 'No model called %s' % model
    tables = []
    for t in db.tables:
       tables.append(t)
    return tables

def getRecords(model, table):
    try: db = eval(model)
    except: return 'No model called %s' % model
    rms = []
    r=db().select(db[table].ALL)
    cols=[c.split('.')[1] for c in r.colnames]
    for rec in r:
        rdict = {}
        for k,v in rec.iteritems():
            if k in cols:
                rdict[k]=v
        rms.append(rdict)
    return rms

def getTableStructure(model, table):
    try: db = eval(model)
    except: return 'No model called %s' % model
    if isinstance(db,SQLDB):
        if table in getTables(model):
            struct = []
            for f in db[table].fields:
                if db[table][f].type.split()[0] == 'reference':
                    struct.append({'column': f,
                                   'type':'.'.join([model,db[table][f].type.split()[1]]),
                                   'required': db[table][f].required})
                else:
                    struct.append({'column':f,
                                   'type':db[table][f].type,
                                   'required': db[table][f].required})
            return struct
        else: return 'No table called %s' % table
    else: return 'No model called %s' % model

def getTypes(model=None):
    l =  ['boolean','string','text','password','blob','upload','integer','double','date','time','datetime']
    if model:
        for t in getTables(model):
            l.append('.'.join([model,t]))
    else:
        for d in getDBs(''):
            for t in getTables(d):
                l.append('.'.join([d,t]) )
    return l

def getDBType(model):
    try: db = eval(model)
    except: return 'No model called %s' % model
    return db._dbname

def doUpdate(model, table, values):
    db = eval(model)
    if isinstance(db, SQLDB):
        if table in getTables(model):
            try:
                db(db[table].id == values['id']).update(**values)
                return "ok"
            except:
                return 'Update failed'
        else: return 'No table called %s' % table
    else: return 'No model called %s' % model

def doInsert(model, table, values):
    if isinstance(values, dict):
        db = eval(model)
        if isinstance(db, SQLDB):
            if table in getTables(model):
                try:
                    db[table].insert(**values)
                    return 'ok'
                except:
                    return 'Insert failed'
            else: return 'No table called %s' % table
        else: return 'No model called %s' % model
    else:
        return 'Must pass the new values in a dictionary'

def doInsert_noValue(model, table):
    db = eval(model)
    if isinstance(db, SQLDB):
        if table in getTables(model):
            try:
                db[table].insert()
                return 'ok'
            except:
                return 'Insert failed'
        else: return 'No table called %s' % table
    else: return 'No model called %s' % model

def doDelete(model, table, id):
    try: db = eval(model)
    except: return 'No model called %s' % model
    try:
        db(db[table].id==id).delete()
        return "ok"
    except:
        return "Delete failed"
        
def doEmpty(model, table):
    try: db = eval(model)
    except: return 'No model called %s' % model
    try:
        db(db[table].id > 0).delete()
        return "ok"
    except:
        return "Empty failed"

def addModel(name, type='sqlite', connstr='dummy.db'):
    if name in getDBs(''):
        return 'Identifier %s already in use' % name
    db=SQLDB('%s://%s' % (type,connstr))
    persist_model(db,name)
    return 'ok'

def addTable(model, tablename):
    try: db = eval(model)
    except: return 'No model called %s' % model
    db.define_table(tablename)
    persist_model(model)
    return 'ok'

def addField(model,table,name,type='string',
                length=32,default=None,required=False,
                ondelete='CASCADE',
                notnull=False,unique=False):
    if type.find('.')>-1:
        if type == '%s.%s'%(model,table):
            return 'Cannot self reference'
        elif type in getTypes(model):
            type = eval(type)
        else:
            return 'Invalid Type'
    newfld = SQLField(name,type,length,default=default,required=required,
        ondelete=ondelete,notnull=notnull,unique=unique)
    try: db = eval(model)
    except: return 'No model called %s' % model
    db[table].fields.append(newfld.name)
    db[table][newfld.name]=newfld
    newfld._tablename=db[table]._tablename 
    newfld._table=db[table]
    newfld._db=db[table]._db
    persist_model(model)
    return 'ok'


def repr_fld(model,fld):
    args = []
    if fld.type.split()[0]=='reference':
        args.append("SQLField('%s',%s"%(fld.name,'.'.join([model,fld.type.split()[1]])))
    else:
        args.append("SQLField('%s','%s'"%(fld.name,fld.type))
    if fld.type=='string' and fld.length<>32:
        args.append("length=%i"%fld.length)
    if fld.default <> None:
        try: args.append('default=%i'%fld.default)
        except: args.append("default='%s'"%fld.default)
    if fld.required:
        args.append("required=True")
    if fld.ondelete<>'CASCADE':
        args.append("ondelete='%s'"%fld.ondelete)
    if fld.notnull:
        args.append("notnull=True")
    if fld.unique:
        args.append("unique=True")
    return ','.join(args)+')'

def repr_tbl(model,tbl):
    import copy
    flds = copy.copy(tbl.fields)
    del flds[flds.index('id')]
    ret = [repr_fld(model,tbl[f]) for f in flds]
    return model+".define_table('%s',\n\t"%tbl._tablename + ',\n\t'.join(ret)+')'

def repr_model(model,name=''):
    if isinstance(model,SQLDB):
        db=model
        model=name
    else:
        db = eval(model)
    m=[]
    m.append(model+'=SQLDB("%s")'%db._uri)
    for t in db.tables:
        m.append(repr_tbl(model,db[t]))
    return '\n'.join(m)

def persist_model(model,name=''):
    import re,shutil
    if isinstance(model,SQLDB):
        modelname = name
    else:
        modelname = model
    n_db = '\n'.join(['# ==%s== GENERATED CODE #'%modelname,repr_model(model,name),'# ==%s== END GENERATED #'%modelname])
    try:
        modelfile = ''.join(open('applications/%s/models/%s.py'%(request.application,model),'rb').readlines())
        rebeg = re.compile('# ==%s== GENERATED CODE #.*# ==%s== END GENERATED #'%(modelname,modelname),re.DOTALL)
        m = rebeg.sub(n_db,modelfile)
        # save old file, just in case we break something
        shutil.copyfile('applications/%s/models/%s.py'%(request.application,modelname),'applications/%s/models/%s.py.bak'%(request.application,modelname))
    except:
        m = n_db
    modelfile = open('applications/%s/models/%s.py'%(request.application,modelname),'wb')
    modelfile.write(m)
    modelfile.close()
    return m

def test_persist():
    return '<pre>'+persist_model('db')+'</pre>'

# Oddly enough, you still need to add every method you want to expose to the Flash/Flex application
services = {'base': base,
            'getDBs': getDBs,
            'getDBTypes': getDBTypes,
            'getTables': getTables,
            'getRecords': getRecords,
            'getTypes': getTypes,
            'getDBType': getDBType,
            'getTableStructure': getTableStructure,
            'doUpdate': doUpdate,
            'doInsert': doInsert,
            'doInsert_noValue': doInsert_noValue,
            'doDelete': doDelete,
            'doEmpty': doEmpty,
            'addModel': addModel,
            'addTable': addTable,
            'addField': addField}

def gateway():
    base_gateway = pyamf.remoting.gateway.BaseGateway(services)
    context = pyamf.get_context(pyamf.AMF0)

    pyamf_request = pyamf.remoting.decode(request.body, context)
    pyamf_response = pyamf.remoting.Envelope(pyamf_request.amfVersion,pyamf_request.clientType)

    for name, message in pyamf_request:
        pyamf_response[name] = base_gateway.getProcessor(message)(message)

    response.headers['Content-Type'] = pyamf.remoting.CONTENT_TYPE
    return pyamf.remoting.encode(pyamf_response, context).getvalue()